package rolesystem.jade.agents;

import rolesystem.core.*;
import rolesystem.jade.*;

import jade.core.*;
import jade.core.behaviours.*;
import jade.lang.acl.*;
import jade.domain.FIPAAgentManagement.*;
import jade.domain.DFService;

import java.io.Serializable;

/**
 * This is the behaviour for agents of rolesystem, which acts as a wrapper
 * layer.
 * An agent of rolesystem has to construct an instance of this behaviour and
 * add it to its list of behaviours. Such agent must provide an instance
 * implementing the interface SubjectLayer, that represents the subject
 * actually participating in rolesystem.
 */
public class WrapperBehaviour extends SimpleBehaviour
{
	/**
	 * Type of service of RoleSystem server, for DF.
	 */
	private static final String SERVICE_TYPE="RoleSystemServer";

	/**
	 * Subject layer of the agent, actually participating in rolesystem.
	 */
	private transient SubjectLayer subject;

	/**
	 * Behaviour to add when the subject layer terminates its job.
	 */
	private transient Behaviour nextBehaviour;
	
	/**
	 * Agent-identifier of the server agent.
	 */
	private transient AID serverAgent;
	
	/**
	 * Object used to signal that a new message is available.
	 */
	private transient Object newMessage;

	/**
	 * Flag indicating whether the SubjectLayer has finished its job.
	 */
	private transient boolean doneSL;

	/**
	 * Flag that is returned by method done().
	 */
	private transient boolean doneB;

	/**
	 * @param agent Agent owner of this behaviour.
	 * @param subject Subject layer of the agent, actually participating
	 * in rolesystem.
	 * @param nextBehaviour Behaviour to add when the SubjectLayer
	 * has finished its job.
	 */
	public WrapperBehaviour(Agent agent, SubjectLayer subject,
		Behaviour nextBehaviour)
	{
		super(agent);
		this.subject=subject;
		this.nextBehaviour=nextBehaviour;
		newMessage=new Object();
	}


	/**
	 * Returns the agent owner of this behaviour.
	 * @return The agent owner of this behaviour.
	 */
	private Agent getAgent()
	{
		return myAgent;
	}


	public void onStart()
	{
		DFAgentDescription dfd = new DFAgentDescription();  
		ServiceDescription sd = new ServiceDescription();

		// Search with the DF for the name of the RoleServer agent
		sd.setType(SERVICE_TYPE);
		dfd.addServices(sd);
		try
		{
			SearchConstraints c = new SearchConstraints();
			DFAgentDescription[] result = DFService.search(myAgent,dfd,c);
			if (result.length > 0)
			{
				serverAgent=result[0].getName();
				new Thread(new SubjectLayerStarter()).start();
				return;
			}
		}
		catch (Exception fe)
		{
			System.err.println(fe);
		}
		// Something went wrong.
		doneSL=true;
	}


	public int onEnd()
	{
		myAgent.addBehaviour(nextBehaviour);
		return super.onEnd();
	}


	public void action()
	{
		synchronized(newMessage)
		{
			if(doneSL)
				doneB=true;
			else
			{
				newMessage.notifyAll();
				block();
			}
		}
	}


	public boolean done()
	{
		return doneB;
	}


	/**
	 * This class allows to start the subject layer in a new thread.
	 */
	private class SubjectLayerStarter implements Runnable
	{
		public void run()
		{
			subject.go(new RoleSystemStub());
			synchronized(newMessage)
			{
				doneSL=true;
				restart();
			}
		}
	}


	/**
	 * This class act as a RoleSystem for the subject embedded in an agent.
	 */
	private class RoleSystemStub implements RoleSystem
	{
		/**
		 * Message template that has to be matched by messages received
		 * in reply to sent ones, with system scope.
		 */
		private MessageTemplate replyMT=
			new MessageTemplate(new ReplyME());


		/**
		 * Sends a command to the server agent and waits for reply.
		 * @param command Command to send.
		 */
		private Serializable sendCommand(RoleCommand command) throws
			RoleException
		{
			ACLMessage msg = new ACLMessage(ACLMessage.REQUEST);
		
			msg.addReceiver(serverAgent);
			try
			{
				msg.setContentObject(new RoleMsg(0, command));
			}
			catch(java.io.IOException ioe)
			{
				System.err.println(ioe);
			}
			getAgent().send(msg);
			synchronized(newMessage)
			{
				for(;;)
				{
					msg=getAgent().receive(replyMT);
					if(msg!=null)
						break;
					try
					{
						newMessage.wait();
					}
					catch (InterruptedException ie)
					{
					}
				}
			}
			try
			{
				if(msg.getPerformative()==ACLMessage.FAILURE)
					throw (RoleException)
						((RoleMsg)msg.getContentObject()).getContent();
				else	//INFORM
					return ((RoleMsg)msg.getContentObject()).getContent();
			}
			catch(RoleException re)
			{
				throw re;
			}
			catch(Exception e)
			{
				System.err.println(e);
				throw new RoleException();
			}
		}


		public RoleRegistration reqRegistration(String role) throws
			RoleException
		{
			Serializable[] params={role};

			// This block is synchronized, so the operation is atomic.
			synchronized(this)
			{
				try
				{
					return new RoleRegistrationStub(((Integer)sendCommand(
						new RoleCommand("reqRegistration", params))).intValue());
				}
				catch(RoleException re)
				{
					throw re;
				}
				catch(Exception e)
				{
					//Unexpected type for content of received message.
					throw new RoleException();
				}
			}
		}


		public int[] searchForRole(String role) throws RoleException
		{
			Serializable[] params={role};

			// This block is synchronized, so the operation is atomic.
			synchronized(this)
			{
				try
				{
					return (int[])sendCommand(
						new RoleCommand("searchForRole", params));
				}
				catch(RoleException re)
				{
					throw re;
				}
				catch(Exception e)
				{
					//Unexpected type for content of received message.
					throw new RoleException();
				}
			}
		}


		public int[] searchForRole(String role, long timeout) throws RoleException
		{
			Serializable[] params={role, new Long(timeout)};

			// This block is synchronized, so the operation is atomic.
			synchronized(this)
			{
				try
				{
					return (int[])sendCommand(
						new RoleCommand("searchForRole", params));
				}
				catch(RoleException re)
				{
					throw re;
				}
				catch(Exception e)
				{
					//Unexpected type for content of received message.
					throw new RoleException();
				}
			}
		}


		public int[] searchForRoleNoWait(String role) throws
			RoleException
		{
			Serializable[] params={role};

			// This block is synchronized, so the operation is atomic.
			synchronized(this)
			{
				try
				{
					return (int[])sendCommand(
						new RoleCommand("searchForRoleNoWait", params));
				}
				catch(RoleException re)
				{
					throw re;
				}
				catch(Exception e)
				{
					//Unexpected type for content of received message.
					throw new RoleException();
				}
			}
		}


		/**
		 * Class introduced for the MessageTemplate that has to be
		 * matched by messages received in reply to sent ones,
		 * with system scope.
		 */
		private class ReplyME implements MessageTemplate.MatchExpression
		{
			public boolean match(ACLMessage msg)
			{
				try
				{
					return serverAgent.equals(msg.getSender()) &&
						(msg.getPerformative()==ACLMessage.INFORM ||
						msg.getPerformative()==ACLMessage.FAILURE) &&
						((RoleMsg)msg.getContentObject()).getScope()==0;
				}
				catch(Exception e)
				{
					return false;
				}
			}		
		}
	}


	/**
	 * This class act as a RoleRegistration for the subject embedded in an
	 * agent.
	 */
	private class RoleRegistrationStub implements RoleRegistration
	{
		/**
		 * Identifier assigned to this registration.
		 * It is also used as scope for messages sent to the server
		 * agent.
		 * When this registration is dismissed, its value is set to 0.
		 */
		private int id;

		/**
		 * Message template that has to be matched by event messages
		 * related to this registration.
		 */
		private MessageTemplate eventMT=
			new MessageTemplate(new EventME());

		/**
		 * Message template that has to be matched by messages received
		 * in reply to sent ones, related to this registration.
		 */
		private MessageTemplate replyMT=
			new MessageTemplate(new ReplyME());
		

		/**
		 * @param id Identifier assigned to this registration.
		 */
		public RoleRegistrationStub(int id)
		{
			this.id=id;
		}


		/**
		 * Check if this registration is valid (i.e. not dismissed).
		 * @throws RoleException If this registration is dismissed.
		 */
		private void check() throws RoleException
		{
			if( id==0 )
				throw new RoleException(RoleException.DISMISSED);
		}


		/**
		 * Sends a command to the server agent and waits for reply.
		 * @param command Command to send.
		 */
		private void sendCommand(RoleCommand command) throws
			RoleException
		{
			ACLMessage msg = new ACLMessage(ACLMessage.REQUEST);
		
			msg.addReceiver(serverAgent);
			try
			{
				msg.setContentObject(new RoleMsg(id, command));
			}
			catch(java.io.IOException ioe)
			{
				System.err.println(ioe);
			}
			getAgent().send(msg);
			synchronized(newMessage)
			{
				for(;;)
				{
					msg=getAgent().receive(replyMT);
					if(msg!=null)
						break;
					try
					{
						newMessage.wait();
					}
					catch (InterruptedException ie)
					{
					}
				}
			}
			try
			{
				if(msg.getPerformative()==ACLMessage.FAILURE)
					throw (RoleException)
						((RoleMsg)msg.getContentObject()).getContent();
			}
			catch(RoleException re)
			{
				throw re;
			}
			catch(Exception e)
			{
				System.err.println(e);
				throw new RoleException();
			}
		}


		/**
		 * Ensures the registration is dismissed.
		 */
		protected void finalize() throws Throwable
		{
			dismiss();
		}


		public RoleEvent listen() throws RoleException
		{
			ACLMessage msg;

			// This block is synchronized, so the operation is atomic.
			synchronized(this)
			{
				check();
				synchronized(newMessage)
				{
					for(;;)
					{
						msg=getAgent().receive(eventMT);
						if(msg!=null)
							break;
						try
						{
							newMessage.wait();
						}
						catch (InterruptedException ie)
						{
						}
					}
				}
				try
				{
					return (RoleEvent)((RoleMsg)msg.getContentObject()).
						getContent();
				}
				catch(Exception e)
				{
					System.err.println(e);
					throw new RoleException();
				}
			}
		}


		public RoleEvent listen(long timeout) throws RoleException
		{
			if(timeout==0)
				return listen();

			ACLMessage msg;
			long finalTime, residualTime;

			// This block is synchronized, so the operation is atomic.
			synchronized(this)
			{
				check();
				finalTime=System.currentTimeMillis()+timeout;
				synchronized(newMessage)
				{
					for(;;)
					{
						msg=getAgent().receive(eventMT);
						residualTime=finalTime-System.currentTimeMillis();
						if(msg!=null || residualTime<=0)
							break;
						try
						{
							newMessage.wait(residualTime);
						}
						catch (InterruptedException ie)
						{
						}
					}
				}
				if(msg==null)
					return null;
				try
				{
					return (RoleEvent)((RoleMsg)msg.getContentObject()).
						getContent();
				}
				catch(Exception e)
				{
					System.err.println(e);
					throw new RoleException();
				}
			}
		}


		public RoleEvent listenNoWait() throws RoleException
		{
			ACLMessage msg;

			// This block is synchronized, so the operation is atomic.
			synchronized(this)
			{
				check();
				msg=getAgent().receive(eventMT);
				if(msg==null)
					return null;
				try
				{
					return (RoleEvent)((RoleMsg)msg.getContentObject()).
						getContent();
				}
				catch(Exception e)
				{
					System.err.println(e);
					throw new RoleException();
				}
			}
		}


		public void doAction(RoleAction action) throws
			RoleException
		{
			Serializable[] params={action};

			// This block is synchronized, so the operation is atomic.
			synchronized(this)
			{
				check();
				sendCommand(new RoleCommand("doAction", params));
			}
		}


		public int whoAmI() throws RoleException
		{
			// This block is synchronized, so the operation is atomic.
			synchronized(this)
			{
				check();
				return id;
			}
		}


		public void dismiss() throws RoleException
		{
			Serializable[] params={};

			// This block is synchronized, so the operation is atomic.
			synchronized(this)
			{
				check();
				sendCommand(new RoleCommand("dismiss", params));
				id=0;
			}
		}


		/**
		 * Class introduced for the MessageTemplate that has to be
		 * matched by event messages related to this registration.
		 */
		private class EventME implements MessageTemplate.MatchExpression
		{
			public boolean match(ACLMessage msg)
			{
				try
				{
					return serverAgent.equals(msg.getSender()) &&
						msg.getPerformative()==ACLMessage.REQUEST &&
						((RoleMsg)msg.getContentObject()).getScope()==id;
				}
				catch(Exception e)
				{
					return false;
				}
			}		
		}


		/**
		 * Class introduced for the MessageTemplate that has to be
		 * matched by messages received in reply to sent ones,
		 * related to this registration.
		 */
		private class ReplyME implements MessageTemplate.MatchExpression
		{
			public boolean match(ACLMessage msg)
			{
				try
				{
					return serverAgent.equals(msg.getSender()) &&
						(msg.getPerformative()==ACLMessage.INFORM ||
						msg.getPerformative()==ACLMessage.FAILURE) &&
						((RoleMsg)msg.getContentObject()).getScope()==id;
				}
				catch(Exception e)
				{
					return false;
				}
			}		
		}
	}
}
